/*
* 
* @license	Apache License 2.0
* @author	Eugene Kuznetsov
*
*/
#include "CDeviceServer.hpp"
#include "CConfiguration.hpp"
#include "CDataBuffer.hpp"
#include "CDevice.hpp"
#include <winsock2.h>
#include <process.h>

void* CDeviceServer::m_pDeviceCriticalSection = 0;
std::vector<CDevice*> CDeviceServer::m_vDevices;
CDeviceServer* CDeviceServer::m_pInstance = 0;

CDeviceServer::CDeviceServer(UInt8 port)
	: m_eState(Link::CLOSED)
	, m_bIsInited(false)
	, m_nPort(port)
	, m_nLink(INVALID_SOCKET)
	, m_nLastError(0)
{
	DBG_L1(("[%p] %s(%d)\n", this, __FUNCTION__, port));
	m_pInstance = this;
}

CDeviceServer::~CDeviceServer()
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	if (m_eState == Link::LISTENING)
	{
		Link::eListenState listen_state = Shutdown();
		if (listen_state != Link::CLOSED)
		{
			DBG_L2(("[%p] listen_state(%d) != Link::CLOSED\n", this, ToInt8(listen_state)));
		}
	}
	while (m_vDevices.size())
	{
		Sleep(1000);
	}
	if (m_pDeviceCriticalSection != 0)
	{
		DeleteCriticalSection(static_cast<CRITICAL_SECTION*>(m_pDeviceCriticalSection));
		delete static_cast<CRITICAL_SECTION*>(m_pDeviceCriticalSection);
	}
	else
	{
		// impossible
	}
	CConfiguration::removeInstance();
}

Base::eState CDeviceServer::Init()
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	if (m_bIsInited)
	{
		DBG_L2(("[%p] already inited\n", this));
		return Base::STATE_OK;
	}
	m_nLink = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (m_nLink == SOCKET_ERROR)
	{
		DBG_L2(("[%p] socket call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		m_nLink = INVALID_SOCKET;
		return Base::STATE_NOT_OK;
	}
	sockaddr_in sai;
	sai.sin_family = AF_INET;
	sai.sin_addr.s_addr = INADDR_ANY;
	sai.sin_port = htons(m_nPort);
	Int16 result = bind(m_nLink, (SOCKADDR*)&sai, sizeof(sai));
	if (result == SOCKET_ERROR)
	{
		DBG_L2(("[%p] bind call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		result = closesocket(m_nLink);
		if (result == SOCKET_ERROR)
		{
			DBG_L2(("[%p] closesocket call -> err:%d\n", this, WSAGetLastError()));
		}
		m_nLink = INVALID_SOCKET;
		return Base::STATE_NOT_OK;
	}
	m_eState = Link::BINDED;
	result = listen(m_nLink, SOMAXCONN);
	if (result == SOCKET_ERROR)
	{
		DBG_L2(("[%p] listen call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		result = closesocket(m_nLink);
		if (result == SOCKET_ERROR)
		{
			DBG_L2(("[%p] closesocket call -> err:%d\n", this, WSAGetLastError()));
		}
		m_nLink = INVALID_SOCKET;
		return Base::STATE_NOT_OK;
	}
	m_eState = Link::LISTENING;
	DBG_L2(("[%p] initialized\n", this));
	m_bIsInited = true;
	return Base::STATE_OK;
}

Base::eState CDeviceServer::AcceptConnection(Int32& client_link, std::string& ip_address, UInt8& port)
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	if (m_nLink == INVALID_SOCKET)
	{
		DBG_L2(("[%p] m_nLink == INVALID_SOCKET\n", this));
		return Base::STATE_NOT_OK;
	}
	client_link = INVALID_SOCKET;
	ip_address.clear();
	port = 0;
	sockaddr_in sin;
	Int16 addrlen = sizeof(sin);
	DBG_L2(("[%p] waiting for incoming connection...\n", this));
	/*
	* @todo
	* replace accept function with select function
	*/
	client_link = accept(m_nLink, (SOCKADDR*)&sin, &addrlen);
	if (client_link == INVALID_SOCKET)
	{
		DBG_L2(("[%p] accept call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		return Base::STATE_NOT_OK;
	}
	port = sin.sin_port;
	ip_address = inet_ntoa(sin.sin_addr);
	DBG_L2(("[%p] accepted new connection -> %s:%d\n", this, ip_address.c_str(), port));
	return Base::STATE_OK;
}

Link::eListenState CDeviceServer::Shutdown()
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	if (m_nLink == INVALID_SOCKET)
	{
		DBG_L2(("[%p] m_nLink == INVALID_SOCKET\n", this));
		return Link::LISTEN_ERROR;
	}
	if (m_eState != Link::REQUEST_CLOSE)
	{
		DBG_L2(("[%p] no request for closing -> m_eState = %d\n", this, ToUInt8(m_eState)));
		return m_eState;
	}
	m_eState = Link::CLOSING;
	Int16 result = shutdown(m_nLink, SD_BOTH);
	if (result == SOCKET_ERROR)
	{
		DBG_L2(("[%p] shutdown call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		m_eState = Link::LISTEN_ERROR;
	}
	result = closesocket(m_nLink);
	if (result == SOCKET_ERROR)
	{
		DBG_L2(("[%p] closesocket call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		m_eState = Link::LISTEN_ERROR;
	}
	else
	{
		DBG_L2(("[%p] listening socket on port %d has been closed\n", this, m_nPort));
		m_eState = Link::CLOSED;
		m_nLink = INVALID_SOCKET;
	}
	return m_eState;
}

inline Link::eListenState CDeviceServer::getState() const
{
	return m_eState;
}

inline void CDeviceServer::setState(Link::eListenState state)
{
	m_eState = state;
}

UInt16 CDeviceServer::getDeviceCount()
{
	return m_vDevices.size();
}

CDeviceServer* CDeviceServer::getInstance()
{
	return m_pInstance;
}

CDevice* CDeviceServer::RegisterDevice(Int32 client_link, std::string ip_address, UInt8 port)
{
	DBG_L1(("[S%.7d] %s(%d, %s, %d)\n", GetCurrentThreadId(), __FUNCTION__, client_link, ip_address.c_str(), port));
	if (m_pDeviceCriticalSection == 0)
	{
		DBG_L1(("[S%.7d] m_pDeviceCriticalSection == 0\n", GetCurrentThreadId()));
		return NULL;
	}
	CDevice * device = new CDevice(client_link, ip_address, port);
	EnterCriticalSection(static_cast<CRITICAL_SECTION*>(m_pDeviceCriticalSection));
	m_vDevices.push_back(device);
	LeaveCriticalSection(static_cast<CRITICAL_SECTION*>(m_pDeviceCriticalSection));
	DBG_L1(("[S%.7d] new device(%p) has been registered\n", GetCurrentThreadId(), device));
	return device;
}

void CDeviceServer::UnRegisterDevice(CDevice* device)
{
	DBG_L1(("[S%.7d] %s(%p)\n", GetCurrentThreadId(), __FUNCTION__, device));
	if (m_pDeviceCriticalSection == 0)
	{
		DBG_L1(("[S%.7d] m_pDeviceCriticalSection == 0\n", GetCurrentThreadId()));
		return;
	}
	EnterCriticalSection(static_cast<CRITICAL_SECTION*>(m_pDeviceCriticalSection));
	std::vector<CDevice*>::iterator it = m_vDevices.begin();
	for (; it != m_vDevices.end(); ++it)
	{
		if (*it == device)
		{
			m_vDevices.erase(it);
			DBG_L1(("[S%.7d] device(%p) has been unregistered\n", GetCurrentThreadId(), device));
			delete device;
			break;
		}
	}
	LeaveCriticalSection(static_cast<CRITICAL_SECTION*>(m_pDeviceCriticalSection));
}

Base::eState CDeviceServer::RunDeviceServer(UInt8 port)
{
	DBG_L1(("[S%.7d] %s\n", GetCurrentThreadId(), __FUNCTION__));
	if (port == 0)
	{
		DBG_L2(("[S%.7d] port == 0, return STATE_NOT_OK\n", GetCurrentThreadId()));
		return Base::STATE_NOT_OK;
	}
	if (m_pDeviceCriticalSection != 0)
	{
		DBG_L2(("[S%.7d] DeviceServer is already running\n", GetCurrentThreadId()));
		return Base::STATE_NOT_OK;
	}
	if (CConfiguration::getInstance() == 0)
	{
		DBG_L2(("[S%.7d] configuration error\n", GetCurrentThreadId()));
		return Base::STATE_NOT_OK;
	}
	m_pDeviceCriticalSection = static_cast<void*>(new CRITICAL_SECTION);
	InitializeCriticalSection(static_cast<CRITICAL_SECTION*>(m_pDeviceCriticalSection));
	HStarterParams* starter_params = new HStarterParams();
	starter_params->m_nPort = port;
	DBG_L1(("[S%.7d] start Device Server thread\n", GetCurrentThreadId()));
	_beginthread(DeviceServerProcess, 0, static_cast<void*>(starter_params));
	return Base::STATE_OK;
}

void CDeviceServer::DeviceServerProcess(void* starter_params)
{
	DBG_L1(("[T%.7d] %s\n", GetCurrentThreadId(), __FUNCTION__));
	HStarterParams* params = static_cast<HStarterParams*>(starter_params);
	CDeviceServer server(params->m_nPort);
	delete params;
	Base::eState state = server.Init();
	if (state != Base::STATE_OK)
	{
		DBG_L2(("[T%.7d] state != Base::STATE_OK\n", GetCurrentThreadId()));
		return;
	}
	while (server.getState() == Link::LISTENING)
	{
		Int32 link;
		std::string ip_address;
		UInt8 port;
		state = server.AcceptConnection(link, ip_address, port);
		if (state == Base::STATE_OK)
		{
			CDevice* device = RegisterDevice(link, ip_address, port);
			DBG_L2(("[S%.7d] start Dispatcher thread\n", GetCurrentThreadId()));
			_beginthread(DispatcherProcess, 0, static_cast<void*>(device));
		}
		else
		{
			/*
			* @todo
			* handle this case
			*/
		}
	}
	DBG_L1(("[T%.7d] %s -> shutting down\n", GetCurrentThreadId(), __FUNCTION__));
}

void CDeviceServer::DispatcherProcess(void* device)
{
	DBG_L2(("[T%.7d] %s\n", GetCurrentThreadId(), __FUNCTION__));
	CDevice* mDevice = static_cast<CDevice*>(device);
	Base::eState state = mDevice->Authorize(AUTH_TIMEOUT);
	if (state != Base::STATE_OK)
	{
		UnRegisterDevice(mDevice);
		DBG_L2(("[T%.7d] %s -> shutting down by device auth failed\n", GetCurrentThreadId(), __FUNCTION__));
		return;
	}
	state = mDevice->StartTargetServers(TARGET_SERVERS_STARTUP_TIMEOUT);
	if (state != Base::STATE_OK)
	{
		UnRegisterDevice(mDevice);
		DBG_L2(("[T%.7d] %s -> shutting down by device failure of target servers startup\n", GetCurrentThreadId(), __FUNCTION__));
		return;
	}
	/*while (getInstance()->getState() == Link::LISTENING)
	{
		// CPU relax time :)
		Sleep(10);
	}*/
	UnRegisterDevice(mDevice);
	DBG_L2(("[T%.7d] %s -> shutting down\n", GetCurrentThreadId(), __FUNCTION__));
}
